﻿
Imports std
Imports lock_t = lock(Of locks.monitorlock)
Imports stack = std.fixed_stack_wrapper(Of eventComb, _4096)

Partial Public Class eventComb
    Public Const end_step As Int64 = maxInt64
    Public Const not_started_step As Int64 = -1
    Public Const first_step As Int64 = 0
    Private Shared ReadOnly needtrace As Boolean
    Private Shared ReadOnly EVENTCOMB_SPINLOCK_WAIT_RATE As Int64
    Private ReadOnly ds() As _do(Of Boolean) = Nothing
    Private ReadOnly _callstack As String = Nothing
    Protected timeouted As void = Nothing
    Protected resetting As void = Nothing
    Private _end_result As ternary = ternary.unknown
    Private finished As void = Nothing
    Private timeoutedEvent As stopwatchEvent = Nothing
    Private _l As lock_t
    Private cb As eventComb = Nothing
    Private [step] As Int64 = not_started_step
    Private pends As UInt64 = 0

    <ThreadStatic()> Private Shared _current As stack
    Public Shared Property current As eventComb
        Get
            Dim c As stack = Nothing
            c = _current
            assert(Not c Is Nothing)
            Return c.back()
        End Get
        Protected Set(ByVal value As eventComb)
            Dim c As stack = Nothing
            c = _current
            If c Is Nothing Then
                c = New stack()
                _current = c
            End If
            If value Is Nothing Then
                c.pop()
            Else
                c.push(value)
            End If
        End Set
    End Property

    Public Shared Function notInEventCombThread()
        Return _current Is Nothing OrElse _current.empty()
    End Function

    Public Shared Function inEventCombThread()
        Return Not notInEventCombThread()
    End Function

    Shared Sub New()
        needtrace = envs.perftrace() OrElse
                    envs.env_value(New String() {"eventComb_trace",
                                                 "eventcombtrace",
                                                 "eventCombTrace",
                                                 "EVENTCOMBTRACE",
                                                 "EVENTCOMB_TRACE"}, Nothing)
        If needtrace Then
            EVENTCOMB_SPINLOCK_WAIT_RATE = counter.registerRateAndLastRate("EVENTCOMB_SPINLOCK_WAIT_RATE")
        End If
    End Sub

    Protected Sub locked(ByVal d As void)
        If needtrace Then
            Dim n As Int64 = 0
            n = Now.milliseconds()
            _l.locked(d)
            If Now.milliseconds() - n > spinwait.halfTimesliceLengthMs Then
                raiseError(errorHandle.errorType.performance,
                           callstack(), ":", [step],
                           " is using ", Now.milliseconds() - n, "ms to wait for another thread to finish")
            End If
        Else
            _l.locked(d)
        End If
    End Sub

    Public Sub reset()
        locked(Sub()
                   [step] = not_started_step
                   _end_result = ternary.unknown
                   pends = 0
                   cb = Nothing
                   begin_ticks() = npos
                   end_ticks() = npos
                   timeoutedEvent = Nothing
                   void_(resetting)
               End Sub)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Protected Sub New(ByVal d() As _do(Of Boolean), ByVal additionalJump As Int64)
        copy(ds, d)
        If needtrace Then
            _callstack = callingcode(additionalJump + 1)
        Else
            _callstack = "##NOT_TRACE##"
        End If
        reset()
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Sub New(ByVal ParamArray d() As _do(Of Boolean))
        Me.New(d, 1)
    End Sub

    Protected Function callstack() As String
        Return _callstack
    End Function

    Public Function end_result_raw() As ternary
        Return _end_result
    End Function

    Public Function end_result() As Boolean
        assert(end_result_raw().notunknown())
        Return end_result_raw().true_()
    End Function

    Public Function pending() As Boolean
        Return pends > 0
    End Function

    Public Function not_pending() As Boolean
        Return pends = 0
    End Function

    Private Sub assert_in_lock()
        If isdebugmode() Then
            assert(_l.held_in_thread())
        End If
    End Sub

    Protected Sub inc_pends()
        assert_in_lock()
        pends += 1
    End Sub

    Protected Sub dec_pends()
        assert_in_lock()
        pends -= 1
    End Sub

    Protected Sub mark_as_failed()
        _end_result = False
    End Sub

    Private Sub clear_pends()
        assert_in_lock()
        pends = 0
    End Sub

    Private Function valid_working_step(ByVal i As Int64) As Boolean
        Return i < ds.Length() AndAlso i >= 0
    End Function

    Private Function valid_step(ByVal i As Int64) As Boolean
        Return valid_working_step(i) OrElse i = end_step OrElse i = not_started_step
    End Function

    Private Sub step_assert()
        assert(valid_step([step]))
    End Sub

    Public Function not_started() As Boolean
        step_assert()
        Return [step] = not_started_step
    End Function

    Public Function working() As Boolean
        step_assert()
        Return valid_working_step([step])
    End Function

    Public Function [end]() As Boolean
        Return in_end_step() AndAlso not_pending()
    End Function

    Private Function in_end_step() As Boolean
        step_assert()
        Return [step] = end_step
    End Function

    Private _begin_ticks As Int64 = npos
    Public Property begin_ticks() As Int64
        Get
            Return _begin_ticks
        End Get
        Private Set(ByVal value As Int64)
            assert(_begin_ticks = npos OrElse value = npos)
            _begin_ticks = value
        End Set
    End Property

    Private _end_ticks As Int64 = npos
    Public Property end_ticks() As Int64
        Get
            Return _end_ticks
        End Get
        Private Set(ByVal value As Int64)
            assert(_end_ticks = npos OrElse value = npos)
            _end_ticks = value
        End Set
    End Property

    Protected Sub _do()
        timelimitOperation.start()

        assert(end_result_raw().unknown_() OrElse in_end_step())
        assert(not_pending())
        assert_in_lock()
        If not_started() Then
            If isemptyarray(ds) Then
                [step] = end_step
            Else
                [step] = first_step
            End If
            begin_ticks() = Now.ticks()
        End If

        Dim laststep As Int64 = 0
        laststep = [step]
        While working() AndAlso not_pending()
            Dim rtn As Boolean = False
            laststep = [step]
            current() = Me
            rtn = do_(ds([step]), False)
            current() = Nothing
            If rtn = False Then
                If isdebugmode() Then
                    raiseError(errorHandle.errorType.warning,
                               "event ", callstack(), ":<step>", laststep, " failed")
                End If
                assert_goto(end_step)
                clear_pends()
            End If
            If in_end_step() Then
                _end_result = rtn
            End If
        End While

        If [end]() Then
            end_ticks() = Now.ticks()
            void_(finished)
            [resume](cb)
        End If

        timelimitOperation.finish(callstack(), ":", laststep)
    End Sub

    Protected Shared Sub [resume](ByVal cb As eventComb)
        If Not cb Is Nothing Then
            'to support waitone
            cb.locked(Sub()
                          If cb.pending() Then
                              cb.dec_pends()
                              If cb.not_pending() Then
                                  cb._do()
                              End If
                          End If
                      End Sub)
        End If
    End Sub

    Friend Sub [do]()
        locked(AddressOf _do)
    End Sub

    Friend Sub setTimeout(ByVal timeoutMs As Int64)
        locked(Sub()
                   timeoutedEvent = stopwatch.push(timeoutMs, AddressOf timeout)
                   assert(Not timeoutedEvent Is Nothing)
                   finished = Sub()
                                  assert(timeoutedEvent.cancel())
                                  timeoutedEvent = Nothing
                              End Sub
               End Sub)
    End Sub

    Friend Sub timeout()
        locked(Sub()
                   If pending() AndAlso object_compare(timeoutedEvent, stopwatchEvent.current()) = 0 Then
                       void_(timeouted)
                       If needtrace Then
                           raiseError(errorHandle.errorType.warning, "event " + callstack() + " timeout")
                       End If
                       [step] = end_step
                       mark_as_failed()
                       clear_pends()
                       _do()
                   End If
               End Sub)
    End Sub

    Private Function _goto(ByVal [step] As Int64) As Boolean
        assert_in_lock()
        If debugassert(valid_step([step])) Then
            Me.step = [step]
            Return True
        Else
            Return False
        End If
    End Function

    Private Sub assert_goto(ByVal [step] As Int64)
        assert(_goto([step]))
    End Sub

    Public Shared Function [goto](ByVal [step] As Int64) As Boolean
        Return current()._goto([step])
    End Function

    Public Shared Function goback() As Boolean
        Return [goto](current().step - 1)
    End Function

    Public Shared Function goto_prev() As Boolean
        Return goback()
    End Function

    Public Shared Function goto_end() As Boolean
        Return [goto](end_step)
    End Function

    Public Shared Function goto_next() As Boolean
        Return [goto](current().[step] + 1)
    End Function

    Public Shared Function goto_last() As Boolean
        Return [goto](current().ds.Length() - 1)
    End Function

    Public Shared Function goto_begin() As Boolean
        Return [goto](0)
    End Function

    Public Shared Function success_behavior(ByVal ec As eventComb,
                                            ByVal suc As _do(Of Boolean),
                                            ByVal fal As void) As Boolean
        If Not ec Is Nothing AndAlso ec.end_result() AndAlso do_(suc, False) Then
            Return True
        Else
            void_(fal)
            Return False
        End If
    End Function

    Public Shared Function success_behavior(ByVal ec As eventComb,
                                            ByVal d As _do(Of Boolean)) As Boolean
        Return success_behavior(ec, d, Nothing)
    End Function

    Public Shared Function success_behavior(ByVal ec As eventComb,
                                            ByVal suc As void,
                                            ByVal fal As void) As Boolean
        Return success_behavior(ec,
                                Function() As Boolean
                                    void_(suc)
                                    Return True
                                End Function,
                                fal)
    End Function

    Public Shared Function success_behavior(ByVal ec As eventComb, ByVal d As void) As Boolean
        Return success_behavior(ec, Function() As Boolean
                                        void_(d)
                                        Return True
                                    End Function)
    End Function
End Class
